/**
 * 购物车模块
 */
import API from '@/api/cart'

export default {
  namespaced: true,
  state() {
    return {
      // 购物车分类信息
      list: []
    }
  },
  // vuex中的计算属性
  getters: {
    // 有效商品列表
    validList(state) {
      // 有效商品 库存大于0 stock 商品有效表示为 true isEffective
      return state.list.filter(item => item.isEffective === true && item.stock > 0)
    },
    // 有效商品总件数
    validTotal(state, getters) {
      return getters.validList.reduce((acc, cur) => {
        return acc + cur.count
      }, 0)
    },
    // 有效商品总价格 (计算价格的时候涉及小数, 要格外注意) 这个东西应该让后端做, 后端有专门处理价格的数据结构
    validPrice(state, getters) {
      return getters.validList.reduce((acc, cur) => {
        return acc + (cur.nowPrice * 100 * cur.count)
      }, 0) / 100
    },
    // 无效商品列表
    invalidList(state) {
      // 无效商品 库存小于0 stock 商品有效表示为 false isEffective
      return state.list.filter(item => !item.isEffective || item.stock <= 0)
    },
    // 已选商品列表 (购物车列表中最左边有一个选中按钮, 标识该商品是否选中)
    selectedList(state, getters) {
      return getters.validList.filter(item => item.selected)
    },
    // 已选商品总件数
    selectedTotal(state, getters) {
      return getters.selectedList.reduce((acc, cur) => {
        return acc + cur.count
      }, 0)
    },
    // 已选商品总金额
    selectedPrice(state, getters) {
      return getters.selectedList.reduce((acc, cur) => {
        return acc + Math.round(cur.nowPrice * 100) * cur.count
      }, 0) / 100
    },
    // 是否全选
    isCheckAll(state, getters) {
      // 要考虑 validList.length === 0 情况
      // every算子特性: 空数组也是返回true。(空数组中所有元素都符合给定的条件，注：因为空数组没有元素)。
      return getters.validList.every(item => item.selected === true) && getters.validList.length
    }
  },
  mutations: {
    /**
     * 加入购物车
     * @param state
     * @param payload
     */
    insertCart(state, payload) {
      // 约定加入购物车字段和后端保持一致 payload对象 的字段
      // 它们是 id skuId name attrsText picture price nowPrice selected stock count isEffective
      // 插入数据规则:
      // 1.先找下是否有相同商品 (指的是skuId相同, 而不是商品id相同)
      // 2.如果有相同的商品, 查询它的数量, 累加到payload上, 再保存最新位置, 原来商品需要删除
      // 3.如果没有相同商品, 保存在最新位置即可
      debugger
      const sameIndex = state.list.findIndex(goods => goods.skuId === payload.skuId)
      if (sameIndex > -1) {
        const count = state.list[sameIndex].count
        payload.count += count
        // 删除原来的 (直接索引替换)
        // state.list.splice(sameIndex, 1)
        state.list[sameIndex] = payload
      } else {
        // 追加新的(在头部位置)
        // todo 不应该在头部位置, 还是原来那个位置, 不然购物车重新选择sku规格信息的时候, 位置发生变化
        state.list.unshift(payload)
        // state.list[sameIndex] = payload
      }

    },
    /**
     * 修改购物车商品
     */
    updateCart(state, goods) {
      // 需要更新goods商品信息这些字段 nowPrice stock isEffective
      // 但是请求API获取最新的商品信息对象字段不固定
      // 对象中有哪些字段就改哪些字段(注意非空过滤), 字段的值合理判断

      // goods 商品对象必须有skuId
      const updateGoods = state.list.find(item => item.skuId === goods.skuId)
      for (const key in goods) {
        if (goods[key] !== undefined && goods[key] !== '' && goods[key] !== null) {
          updateGoods[key] = goods[key]
        }
      }
    },
    /**
     * 删除购物车商品
     */
    deleteCart(state, skuId) {
      if (skuId) {
        const index = state.list.findIndex((goods) => goods.skuId === skuId)
        state.list.splice(index, 1)
      }
    },
    /**
     * 购物车选定规格时, 如果按照原来插入购物车逻辑, 变更规格会把位置更新到最上面,
     * 理论说更新购物车规格时, 这个位置应该保持不变才对
     *
     * @param state
     * @param oldGoodsIndex 购物车改变之前的商品索引
     * @param newGoods      购物车改变之后的商品信息
     */
    replaceCart(state, {
      oldGoodsIndex,
      newGoods
    }) {
      if (oldGoodsIndex >= 0 && oldGoodsIndex <= state.list.length - 1) {
        // todo 举例:  index: 2  黑色41  index: 3  黑色42
        //            操作购物车里面的规格, 操作index: 3 黑色41 --> index: 2 和 3数量合并
        //            存在一个合并购物车操作, 数量++
        // 当改变购物车规格后, 发现有一样规格的商品, 就要将这个两个合并
        const sameIndex = state.list.findIndex(goods => goods.skuId === newGoods.skuId)
        if (sameIndex > -1) {
          const count = state.list[sameIndex].count
          newGoods.count += count
          // 将sameIndex索引数据替换成最新的
          state.list[sameIndex] = newGoods
          // 删除oldGoodsIndex索引
          state.list.splice(oldGoodsIndex, 1)
        } else {
          // 没有发现一样规格商品就直接替换即可
          state.list[oldGoodsIndex] = newGoods
        }
      }
    },
    /**
     * 设置购物车
     * @param state
     * @param payload
     */
    setCart(state, payload) {
      // 如果payload 为空数组, 就是清空. 有值就是设置
      state.list = payload
    }
  },
  // 异步操作
  actions: {
    /**
     * 合并购物车 | 一定是已登录状态
     * @param ctx
     */
    async mergeCart(ctx) {
      // 准备合并参数
      const cartList = ctx.state.list.map(item => {
        return {
          skuId: item.skuId,
          selected: item.selected,
          count: item.count
        }
      })

      // 加上了async之后, 这个函数的返回值就是Promise
      await API.mergeCart(cartList)
      // 合并成功, 用async / await之后, 购物车合并完了之后, 才能跳转
      ctx.commit('setCart', [])
    },
    /**
     * 修改购物车 (选中状态, 数量) | 修改购物车信息也是区分本地修改和登录情况修改
     * @param ctx
     * @param payload payload必须有 skuId, 可能: selected count
     */
    updateCart(ctx, payload) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          // 登录
          API.updateCart(payload.skuId, payload.selected, payload.count)
            .then(() => {
              // 获取购物车列表
              API.getCartList()
                .then(res => {
                  ctx.commit('setCart', res.result)
                  resolve()
                })
            })
        } else {
          // 未登录
          ctx.commit('updateCart', payload)
          resolve()
        }
      })
    },
    /**
     * 全选与取消全选
     */
    checkAllCart(ctx, selected) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          // 登录
          const ids = ctx.getters.validList.map(item => item.skuId)
          API.checkAllCart(selected, ids)
            .then(() => {
              // 获取购物车列表
              API.getCartList()
                .then(res => {
                  ctx.commit('setCart', res.result)
                  resolve()
                })
            })
        } else {
          // 未登录情况下
          // 这里并没有去修改计算属性, 而是从计算属性中找出需要修改的值, 调用commit去修改state状态
          ctx.getters.validList.forEach(item =>
            ctx.commit('updateCart', {
              skuId: item.skuId,
              selected
            })
          )
          resolve()
        }
      })
    },
    /**
     * 加入购物车
     * @param ctx  对象包含一下属性 state | rootState | commit | dispatch | getters | rootGetters
     * @param payload
     */
    insertCart(ctx, payload) {
      // 是否登录
      return new Promise((resolve, reject) => {
        // ctx.state      // 等同于 `store.state`, 若在模块中则为局部状态
        // ctx.rootState  // 等同于 `store.rootState`, 只存在与模块中
        if (ctx.rootState.user.profile.token) {
          // 已登录
          API.insertCart(payload.skuId, payload.count)
            .then(() => {
              // 获取购物车列表
              API.getCartList()
                .then(res => {
                  ctx.commit('setCart', res.result)
                  resolve()
                })
            })
        } else {
          // 未登录
          // 这里不用加模块了,  不用这样写ctx.dispatch('cart/insertCart')
          // bug点, 在action写了 dispatch了
          ctx.commit('insertCart', payload)
          // todo 如何理解: 调用`成功的回调函数CB`, 不给回调函数传递参数
          resolve()
        }
      })
    },
    /**
     * 请求API获取购物车商品列表
     */
    getCart(ctx) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          // 已登录
          API.getCartList()
            .then((res) => {
              ctx.commit('setCart', res.result)
              // 这样一定要有, 这样调用者才能使用 .then()方法
              resolve()
            })
        } else {
          // 未登录
          // 同时发送请求(有几个商品发几个请求)等所有请求成功, 一并去修改本地数据
          const promiseArr = ctx.state.list.map(goods => {
            return API.getNewCartGoods(goods.skuId)
          })

          // Promise.all(promiseArr).then((dataList) => {})
          // 同时发请求, 所有请求发送成功
          // dataList成功结果的集合, 数据顺序和promiseArr顺序一致, 它又是根据state.list而来
          Promise.all(promiseArr)
            .then(dataList => {
              console.log(dataList)
              // 更新本地购物车
              dataList.forEach((data, index) => {
                ctx.commit('updateCart', { skuId: ctx.state.list[index].skuId, ...data.result })
              })
              // 调用resolve代表更新成功
              resolve()
            })
            .catch(err => {
              reject(err)
            })
        }
      })
    },
    /**
     * 删除购物车商品
     */
    deleteCart(ctx, skuId) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          // 已登录
          API.deleteCart([skuId])
            .then(() => {
              // 获取购物车列表
              API.getCartList()
                .then(res => {
                  ctx.commit('setCart', res.result)
                  resolve()
                })
            })
        } else {
          // 未登录
          ctx.commit('deleteCart', skuId)
          // 调用resolve代表更新成功
          resolve()
        }
      })
    },
    /**
     * 批量删除已选中状态的商品
     * @param ctx
     * @param isClear
     */
    batchDeleteCart(ctx, isClear) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          const skuIds = ctx.getters[isClear ? 'invalidList' : 'selectedList']
            .map(item => item.skuId)
          API.deleteCart(skuIds)
            .then(() => {
              // 获取购物车列表
              API.getCartList()
                .then(res => {
                  ctx.commit('setCart', res.result)
                  resolve()
                })
            })
        } else {
          // 未登录
          // 找出选中/失效的商品列表, 遍历调用删除的mutations
          ctx.getters[isClear ? 'invalidList' : 'selectedList']
            .forEach((item) => {
              ctx.commit('deleteCart', item.skuId)
              resolve()
            })
        }
      })
    },
    /**
     * 修改购物车的sku规格信息
     * @param ctx
     * @param oldSkuId    改变之前的skuId
     * @param newSkuInfo  改变后部分sku信息
     */
    updateCartSku(ctx, {
      oldSkuId,
      newSkuInfo
    }) {
      return new Promise((resolve, reject) => {
        if (ctx.rootState.user.profile.token) {
          // 已登录
          // 没有修改规格API接口, 但是可以用删除旧的商品规格, 然后构建新的商品规格, 并加入购物车
          const oldGoods = ctx.state.list.find((item) => item.skuId === oldSkuId)
          API.deleteCart([oldGoods.skuId])
            .then(() => {
              API.insertCart(newSkuInfo.skuId, oldGoods.count)
                .then(() => {
                  // 获取购物车列表
                  API.getCartList()
                    .then(res => {
                      ctx.commit('setCart', res.result)
                      resolve()
                    })
                })
            })
        } else {
          // 未登录
          // 1.找出旧的商品信息索引
          // const oldGoods = ctx.state.list.find((item) => item.skuId === oldSkuId)
          const oldGoodsIndex = ctx.state.list.findIndex((item) => item.skuId === oldSkuId)
          // 2.一开始准备是得到旧的商品信息, 因为skuId已经变化了, 所以这个商品信息就没有意义了
          //   但旧的商品信息在数组中位置有意义的, 更新规格应该是一个替换数组指定索引元素操作
          //   但是也会出现选定一个规格后, 与已存在的购物车同类型spu商品的sku一直问题, 这时就要合并
          if (oldGoodsIndex !== -1) {
            // ctx.commit('deleteCart', oldSkuId)

            // 3.根据新的sku信息和旧的商品信息, 合并成一个新的购物车商品数据
            const {
              skuId,
              price: nowPrice,
              specsText: attrsText,
              inventory: stock
            } = newSkuInfo

            const newGoods = {
              ...ctx.state.list[oldGoodsIndex],
              skuId,
              nowPrice,
              attrsText,
              stock
            }
            // 4.将原来旧的商品替换成新的位置
            ctx.commit('replaceCart', {
              oldGoodsIndex,
              newGoods
            })

            // ctx.commit('insertCart', newGoods)
            resolve()
          }
        }
      })
    }
  }
}
